/* Copyright - Apache License 2.0
 * 
 * The project "kyou" is
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *      http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.kyou.pack.segment.param;

import java.lang.reflect.InvocationTargetException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;

import net.kyou.exception.KyouErr;
import net.kyou.exception.KyouException;
import net.kyou.pack.PackContext;
import net.kyou.pack.segment.Segment;

/**
 * 表示在参数段中可以定义的参数
 * <p>
 * <li>参数用于将报文元素的有关信息通过指定的方式计算出来后传给相应的参数段以输出到报文流中。</li>
 * <li>各种不同的参数之间可以在保证类型正确的前提下自由组合，组合方法是将参数前后并列，以空格分隔，则此时后面的参数的计算结果将会传给前面的参数。即计算顺序是从后往前的。</li>
 * <li>当然也有一些参数不接受其它参数的计算结果。</li>
 * <li>参数有着不同的返回类型和接受类型，参数段也有各自的接受类型，需要确保类型匹配，否则会抛异常。</li>
 * <li>特别的，可以使用数字字面量。数字字面量将作为一个返回类型为Int的参数参与运算。</li>
 * </p>
 * <p>
 * 例：
 * <li>n 表示求当前报文元素的名称</li>
 * <li>v 表示求当前报文元素的值</li>
 * <li>lens n 表示求当前报文元素的名称的长度</li>
 * <li>lenb s2b.utf8 v 表示求当前报文元素的值，并以utf8编码转成字节数组，并返回这个字节数组的长度</li>
 * </p>
 * 
 * @author NuclearG (<a href="mailto:nuclearg@163.com">nuclearg@163.com</a>)
 */
public abstract class Param {
    /**
     * 各种Segment的实现类的缓存
     */
    private final static Map<String, Class<? extends Param>> classes = new HashMap<String, Class<? extends Param>>();
    static {
        init(NameParam.class);
        init(ValueParam.class);
        init(MemberParam.class);

        init(ConvertB2SParam.class);
        init(ConvertS2BParam.class);
        init(ConvertI2SParam.class);
        init(ConvertS2IParam.class);

        init(LenBParam.class);
        init(LenSParam.class);
    }

    /**
     * 初始化一个Param实例
     * 
     * @param expr
     *            参数表达式
     * @param segment
     *            该参数所属的段实例
     * @param encoding
     * @return 建立起的Param实例
     */
    public static Param __new(String expr, Segment segment, Charset encoding) {
        try {
            if (expr == null || expr.trim().length() == 0)
                throw new KyouException(KyouErr.Style.NullParamExpr);

            expr = expr.trim();

            // 判断表达式是否有多个参数
            if (!expr.contains(" "))
                // 创建单元参数
                return unary(expr, segment, encoding);
            else {
                // 创建双元参数

                int pos = expr.indexOf(' ');

                // 以第一个空格为分隔符，递归创建后面的参数
                Param param = __new(expr.substring(pos + 1), segment, encoding);

                // 创建当前参数
                return binary(expr.substring(0, pos), segment, param, encoding);
            }
        } catch (Throwable ex) {
            throw new KyouException(KyouErr.Style.InitParamFail, expr, ex);
        }
    }

    /**
     * 检查某个参数的计算结果类型是否与期望的参数类型吻合
     * 
     * @param param
     *            参数
     * @param expect
     *            作为容器的段期望的参数类型
     */
    public static void __checkType(Param param, ParamType expect) {
        ParamType type = param.getClass().getAnnotation(ParamTag.class).type();

        if (type != expect)
            throw new KyouException(KyouErr.Style.InvalidParamResultType, "type: " + type + " expect: " + expect);
    }

    /**
     * 工具函数 创建一个单元参数
     */
    private static Param unary(String expr, Segment segment, Charset encoding) throws Throwable {
        // PlainIntParam?
        try {
            return new PlainIntParam(expr, segment, encoding);
        } catch (Exception ex) {
        }

        Class<? extends Param> cls = select(expr);
        if (!UnaryParam.class.isAssignableFrom(cls))
            throw new KyouException(KyouErr.Style.ParamShouldUnary);

        try {
            return cls.getDeclaredConstructor(UnaryParam.class.getDeclaredConstructors()[0].getParameterTypes()).newInstance(expr, segment, encoding);
        } catch (InvocationTargetException ex) {
            throw ex.getCause();
        }
    }

    /**
     * 工具函数 创建一个双元参数
     */
    private static Param binary(String expr, Segment segment, Param arg, Charset encoding) throws Throwable {
        Class<? extends Param> cls = select(expr);
        if (!BinaryParam.class.isAssignableFrom(cls))
            throw new KyouException(KyouErr.Style.ParamShouldBinary);

        try {
            return cls.getDeclaredConstructor(BinaryParam.class.getDeclaredConstructors()[0].getParameterTypes()).newInstance(expr, segment, arg, encoding);
        } catch (InvocationTargetException ex) {
            throw ex.getCause();
        }
    }

    /**
     * 工具函数 根据表达式选择出相应的参数实现类
     */
    private static Class<? extends Param> select(String expr) {
        String body = expr.contains(".") ? expr.substring(0, expr.indexOf('.')) : expr;
        if (classes.containsKey(body))
            return classes.get(body);
        else
            throw new KyouException(KyouErr.Style.InvalidParamTag, expr);
    }

    /**
     * 工具函数 将一个Param实现类的信息写入缓存
     */
    static void init(Class<? extends Param> cls) {
        ParamTag tag = cls.getAnnotation(ParamTag.class);
        classes.put(tag.name(), cls);
    }

    /**
     * 该参数所属的段实例
     */
    public final Segment segment;
    /**
     * 参数的本体
     */
    final String body;
    /**
     * 参数的后缀 如果未提供后缀则为null
     */
    final String postfix;

    /**
     * 初始化Param
     */
    Param(String expr, Segment segment, Charset encoding) {
        this.segment = segment;

        if (expr.contains(".")) {
            int pos = expr.indexOf('.');
            this.body = expr.substring(0, pos);
            this.postfix = expr.substring(pos + 1);
        } else {
            this.body = expr;
            this.postfix = null;
        }

        this.checkPostfix();
    }

    /**
     * 计算该参数
     * 
     * @param context
     *            组包上下文
     * @return 参数的计算结果
     */
    public Object __eval(PackContext context) {
        return this.eval(context);
    }

    /**
     * 计算该参数
     * 
     * @param context
     *            组包上下文
     * @return 该参数的计算结果
     */
    abstract Object eval(PackContext context);

    /**
     * 工具函数 检查为当前参数提供的后缀是否符合参数的声明
     */
    private void checkPostfix() {
        ParamTag tag = this.getClass().getAnnotation(ParamTag.class);
        switch (tag.postfix()) {
            case Irrelevant:
                // 不进行任何检查
                break;
            case Empty:
                if (this.postfix != null)
                    throw new KyouException(KyouErr.Style.ParamPostfixNotRequired, this.body + "." + this.postfix);
                break;
            case Required:
                if (this.postfix == null)
                    throw new KyouException(KyouErr.Style.ParamPostfixRequired, this.body);
                break;
            case Dual:
                // 不做判断
                break;
            case RequiresInt:
                if (this.postfix == null)
                    throw new KyouException(KyouErr.Style.ParamPostfixRequired, this.body);
                try {
                    int v = Integer.parseInt(this.postfix);
                    if (v < 0)
                        throw new KyouException(KyouErr.Style.ParamPostfixRequiresPositiveInteger, this.body + "." + this.postfix);
                } catch (NumberFormatException ex) {
                    throw new KyouException(KyouErr.Style.ParamPostfixRequiresPositiveInteger, this.body + "." + this.postfix);
                }
                break;
            case DualInt:
                if (this.postfix != null)
                    try {
                        int v = Integer.parseInt(this.postfix);
                        if (v < 0)
                            throw new KyouException(KyouErr.Style.ParamPostfixRequiresPositiveInteger, this.body + "." + this.postfix);

                    } catch (NumberFormatException ex) {
                        throw new KyouException(KyouErr.Style.ParamPostfixRequiresPositiveInteger, this.body + "." + this.postfix);
                    }
                break;
        }
    }
}
